﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Text;
using System.Web.Security;

namespace Sfd.SqlAzureInstaller
{
    public class Program
    {
        private static string _databaseName = "sfdDatabase";
        private const string AdminRoleName = "admin";
        private static readonly string[] Scripts = new[] { "InstallCommon.sql", "InstallMembership.sql", "InstallRoles.sql", "InstallSfdTables.sql" };

        private static string _serverName;
        private static string _userName;
        private static string _password;
        private static string _sfdUserName;
        private static string _sfdPassword;
        private static bool _createAdminUserOnly;

        public static void Main(string[] args)
        {
            ParseCommandLine(args);
            CheckArguments();
            CheckFiles();
            CheckProviders();
            if (!_createAdminUserOnly)
            {
                EnsureDatabaseExists();
                RunScripts();
            }

            CreateAdminUser();

            Console.WriteLine("----------------------------------------");
            Console.WriteLine("      Création de la base effectuée avec succès");
            Console.WriteLine("----------------------------------------");
        }

        /// <summary>
        /// Analyse les arguments
        /// </summary>
        /// <param name="args"></param>
        /// <remarks>Les arguments doivent être les suivants :
        /// <para>S:[SqlAzureServerName]</para>
        /// <para>U:[UserName]</para>
        /// <para>P:[Password]</para>
        /// <para>sfdU:[SfdUserName]</para>
        /// <para>sfdP:[SfdPassword]</para>
        /// <para>Option: si vous ajoutez -u, seul la création de l'administrateur sera effectué</para>
        /// <para>Option; D:databaseName. Vous pouvez spécifier le nom de la base de données. Par défaut sfdDatabase</para>
        /// <para>Exemple : Sfd.SqlAzureInstaller S:e4s2g8f652 U:Richard P:myP@ssw0rd sfdU:RichardClark sfdP:m0n$Password</para>
        /// </remarks>
        private static void ParseCommandLine(IEnumerable<string> args)
        {
            if (args == null) throw new ArgumentNullException("args");

            foreach (var arg in args)
            {
                if (arg.StartsWith("S:"))
                {
                    _serverName = arg.Substring(2);
                }
                else if (arg.StartsWith("U:"))
                {
                    _userName = arg.Substring(2);
                }
                else if (arg.StartsWith("P:"))
                {
                    _password = arg.Substring(2);
                }
                else if (arg.StartsWith("sfdU:"))
                {
                    _sfdUserName = arg.Substring(5);
                }
                else if (arg.StartsWith("sfdP:"))
                {
                    _sfdPassword = arg.Substring(5);
                }
                else if (arg.StartsWith("-u"))
                {
                    _createAdminUserOnly = true;
                }
                else if(arg.StartsWith("D:"))
                {
                    _databaseName = arg.Substring(2);
                }
                else
                    throw new ArgumentException(string.Format("argument non reconnu: {0}", arg), "args");
            }
        }

        /// <summary>
        /// Valide que les arguments sont corrects (pas d'oubli)
        /// </summary>
        private static void CheckArguments()
        {
            if (string.IsNullOrEmpty(_serverName))
                throw new ArgumentException("Nom du serveur SQl Azure inconnu");
            if (string.IsNullOrEmpty(_userName))
                throw new ArgumentException("Nom du userName SqlAzure inconnu");
            if (string.IsNullOrEmpty(_password))
                throw new ArgumentException("Mot de passe du serveur SQl Azure inconnu");
            if (string.IsNullOrEmpty(_sfdUserName))
                throw new ArgumentException("Nom de l'utilisateur administrateur de Sfd inconnu");
            if (string.IsNullOrEmpty(_sfdPassword))
                throw new ArgumentException("Mot de passe de l'utilisateur administrateur de Sfd inconnu"); 
        }

        private static void CheckProviders()
        {
            var myMembershipProvider = Membership.Provider as MyMembershipProvider;

            if (myMembershipProvider == null)
            {
                throw new InvalidOperationException("La section membership n'est pas renseignée");
            }

            var myRoleProvider = Roles.Provider as MyRoleProvider;

            if (myRoleProvider == null)
            {
                throw new InvalidOperationException("La section rolemanager n'est pas renseignée");
            }
        }

        /// <summary>
        /// Vérification de la présence des scripts sql
        /// </summary>
        private static void CheckFiles()
        {
            CheckFile("InstallCommon.sql");
            CheckFile("InstallMembership.sql");
            CheckFile("InstallRoles.sql");
            CheckFile("InstallSfdTables.sql");
        }

        /// <summary>
        /// Vérification de la présence d'un fichier
        /// </summary>
        /// <param name="fileName"></param>
        private static void CheckFile(string fileName)
        {
            if (!File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName)))
                throw new InvalidOperationException(string.Format("Le fichier {0} est introuvable", fileName));
        }

        /// <summary>
        /// On s'assure que la base de données <see cref="_databaseName"/> existe bien. Si elle n'existe pas, on la créée.
        /// </summary>
        private static void EnsureDatabaseExists()
        {
            using (var conn = new SqlConnection(GetMasterConnectionString()))
            {
                conn.Open();

                using (var command = new SqlCommand(string.Format("SELECT COUNT(*) FROM sys.databases WHERE name = N'{0}'", _databaseName), conn))
                {
                    var result = command.ExecuteScalar();
                    if (result != null && (int)result == 1)
                        return;
                }

                using (var command = new SqlCommand(string.Format("CREATE DATABASE [{0}]", _databaseName), conn))
                {
                    command.ExecuteScalar();
                }

            }
        }

        internal static string GetConnectionString()
        {
            return
                string.Format(
                    "Data Source=tcp:{0}.database.windows.net;Initial Catalog={1};Persist Security Info=True;User ID={2};Password={3}",
                    _serverName,
                    _databaseName,
                    _userName,
                    _password);
        }

        private static string GetMasterConnectionString()
        {
            return
                string.Format(
                    "Data Source=tcp:{0}.database.windows.net;Initial Catalog={1};Persist Security Info=True;User ID={2};Password={3}",
                    _serverName,
                    "master",
                    _userName,
                    _password);
        }

        /// <summary>
        /// Connects to s_database and runs the requested scripts.
        /// </summary>
        private static void RunScripts()
        {
            // Connect to s_database and run requested scripts
            using (var conn = new SqlConnection(GetConnectionString()))
            {
                conn.Open();
                conn.InfoMessage += (sender, message) => Console.WriteLine(message.Message);
                foreach (var script in Scripts)
                {
                    using (var fstream = File.Open(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, script), FileMode.Open, FileAccess.Read))
                    {
                        foreach (var batch in ReadSqlBatches(fstream).Where(batch => batch != string.Empty))
                        {
                            using (var cmd = conn.CreateCommand())
                            {
                                cmd.CommandText = batch;
                                cmd.CommandType = System.Data.CommandType.Text;
                                cmd.ExecuteNonQuery();
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Iterator which returns consecutive sql batches from a test stream.  A Sql Batch is a consecutive series of 
        /// Sql Statements followed by a "GO" statement.
        /// </summary>
        /// <param name="sqlStream">Text stream from which to read batches.</param>
        private static IEnumerable<string> ReadSqlBatches(Stream sqlStream)
        {
            var reader = new StreamReader(sqlStream);
            var statement = new StringBuilder(string.Empty);

            while (true)
            {
                var nextLine = reader.ReadLine();

                // If this is the last line, return the current batch and break.
                if (nextLine == null)
                {
                    yield return statement.ToString();
                    yield break;
                }

                nextLine = nextLine.Trim();

                // If this line is a "GO" statement, return the current batch.
                if (string.Equals(nextLine, "GO", StringComparison.InvariantCultureIgnoreCase))
                {
                    yield return statement.ToString();
                    statement = new StringBuilder(string.Empty);
                    continue;
                }

                if (nextLine == string.Empty)
                {
                    continue;
                }

                // If this line is a single-line comment, just skip the line
                if (nextLine.StartsWith("--"))
                {
                    continue;
                }

                statement.AppendLine(nextLine);
            }
        }

        private static void CreateAdminUser()
        {
            Console.WriteLine("----------------------------------------");
            Console.WriteLine("      Création de l'administrateur");
            Console.WriteLine("----------------------------------------");

            // création du role d'administrateur
            if (!Roles.RoleExists(AdminRoleName))
                Roles.CreateRole(AdminRoleName);

            // création du user administrateur
            if (Membership.GetUser(_sfdUserName) != null)
                Membership.DeleteUser(_sfdUserName);

            try
            {
                Membership.CreateUser(_sfdUserName, _sfdPassword);
            }
            catch (MembershipCreateUserException exception)
            {
                Console.WriteLine("Impossible de créer l'administrateur:\r\n{0}", exception.Message);
                return;
            }
            
            // ajout du user dans le rôle
            Roles.AddUserToRole(_sfdUserName, AdminRoleName);
        }


    }
}
